package com.bihua.shop.service;

import java.time.Duration;
import java.util.function.Supplier;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bihua.common.constant.CacheConstants;
import com.bihua.common.constant.Constants;
import com.bihua.common.core.domain.event.LogininforEvent;
import com.bihua.common.enums.DeviceType;
import com.bihua.common.enums.LoginType;
import com.bihua.common.enums.UserType;
import com.bihua.common.exception.user.CaptchaException;
import com.bihua.common.exception.user.CaptchaExpireException;
import com.bihua.common.exception.user.UserException;
import com.bihua.common.utils.BeanCopyUtils;
import com.bihua.common.utils.DateUtils;
import com.bihua.common.utils.MessageUtils;
import com.bihua.common.utils.ServletUtils;
import com.bihua.common.utils.StringUtils;
import com.bihua.common.utils.redis.RedisUtils;
import com.bihua.common.utils.spring.SpringUtils;
import com.bihua.shop.domain.TShopUser;
import com.bihua.shop.domain.model.ShopLoginUser;
import com.bihua.shop.domain.vo.TShopUserVo;
import com.bihua.shop.helper.ShopLoginHelper;
import com.bihua.shop.mapper.TShopUserMapper;
import com.bihua.system.service.ISysConfigService;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 登录校验方法
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class ShopLoginService {

    private final TShopUserMapper baseMapper;
    private final ISysConfigService configService;
    private final WeixinService weixinService;

    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;


    /**
     * 登录验证
     *
     * @param phoneNumber 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String phoneNumber, String password, String code, String uuid) {
        HttpServletRequest request = ServletUtils.getRequest();
        String captchaEnabledStr = configService.selectConfigByKey("blog.account.captchaEnabled");
        boolean captchaEnabled =true;
        if (StringUtils.isNotEmpty(captchaEnabledStr)) {
            captchaEnabled = Convert.toBool(captchaEnabled);
        }
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(phoneNumber, code, uuid, request);
        }
        TShopUserVo user = loadUserByPhonenumber(phoneNumber, false);
        checkLogin(LoginType.PASSWORD, phoneNumber, () -> !BCrypt.checkpw(password, user.getPassword()));
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        ShopLoginUser loginUser = buildLoginUser(user);
        // 生成token
        ShopLoginHelper.loginByDevice(loginUser, DeviceType.PC);

        recordLogininfor(phoneNumber, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        recordLoginInfo(user.getId(), phoneNumber);
        return StpUtil.getTokenValue();
    }

    public String smsLogin(String phonenumber, String smsCode) {
        // 通过手机号查找用户
        TShopUserVo user = loadUserByPhonenumber(phonenumber, true);

        checkLogin(LoginType.SMS, user.getNickName(), () -> !validateSmsCode(phonenumber, smsCode));
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        ShopLoginUser loginUser = buildLoginUser(user);
        // 生成token
        ShopLoginHelper.loginByDevice(loginUser, DeviceType.APP);

        recordLogininfor(user.getNickName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        recordLoginInfo(user.getId(), user.getNickName());
        return StpUtil.getTokenValue();
    }

    public boolean bind(String xcxCode){
        String openId = weixinService.getOpenIdByCode(xcxCode);
        if (StringUtils.isEmpty(openId)) {
            throw new UserException("获取微信消息失败，无法绑定微信用户，请重试！");
        }
        ShopLoginUser loginUser;
        try{
            loginUser = ShopLoginHelper.getLoginUser();
        } catch (NotLoginException ignored) {
            loginUser = null;
        }
        if(ObjectUtil.isEmpty(loginUser)){
            throw new UserException("未登录无法绑定微信用户，请先登录！");
        }
        if(StringUtils.isEmpty(loginUser.getWechatOpenId())){
            loginUser.setWechatOpenId(openId);
            ShopLoginHelper.setLoginUser(loginUser);
            xcxRecordLoginInfo(loginUser.getId(), openId);
            return true;
        }else if(StringUtils.equals(loginUser.getWechatOpenId(), openId)){
            return true;
        }else{
            throw new UserException("当前手机号已绑定其他微信用户");
        }
    }
    public String xcxLogin(String xcxCode) {
        // xcxCode 为 小程序调用 wx.login 授权后获取
        // todo 以下自行实现
        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
        String openId =  weixinService.getOpenIdByCode(xcxCode);
        if (StringUtils.isEmpty(openId)) {
            throw new UserException("获取微信消息失败");
        }
        LambdaQueryWrapper<TShopUser> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(openId), TShopUser::getWechatOpenId, openId);
        TShopUserVo currentWechatUser = baseMapper.selectVoOne(lqw);
        if (currentWechatUser != null) {
            // 此处可根据登录用户的数据不同 自行创建 loginUser
            ShopLoginUser loginUser = buildLoginUser(currentWechatUser);
            loginUser.setRefreshWechatInfo(false);
            // 生成token
            ShopLoginHelper.loginByDevice(loginUser, DeviceType.XCX);

            recordLogininfor(String.valueOf(currentWechatUser.getId()), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
            xcxRecordLoginInfo(currentWechatUser.getId(),openId);
            return StpUtil.getTokenValue();
        }else{
            throw new UserException("未绑定手机号,请先登录绑定！");
        }
    }

    private String LoggedIn(String openId, ShopLoginUser loginUser) {
        if (StringUtils.isNotEmpty(loginUser.getWechatOpenId())
                && !loginUser.getWechatOpenId().equals(openId)) {
            throw new UserException("当前手机号已绑定其他微信用户");
        } else {
            recordLogininfor(String.valueOf(loginUser.getId()), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
            xcxRecordLoginInfo(loginUser.getId(), openId);
            //当前用户通过手机登陆后获取微信信息
            return StpUtil.getTokenValue();
        }
    }

    private TShopUserVo loadUserByPhonenumber(String phonenumber, boolean isCreateUser) {
        LambdaQueryWrapper<TShopUser> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(phonenumber), TShopUser::getMobile, phonenumber);
        TShopUserVo user = baseMapper.selectVoOne(lqw);
        if (ObjectUtil.isNull(user)) {
            if(isCreateUser){
                log.info("登录用户：{} 不存在.", phonenumber);
                TShopUser shopUser = new TShopUser();
                shopUser.setMobile(phonenumber);
                String password = RandomUtil.randomNumbers(6);
                shopUser.setPassword(BCrypt.hashpw(password));
                boolean regFlag = baseMapper.insert(shopUser) > 0;
                if (!regFlag) {
                    throw new UserException("user.register.error");
                }
                user = BeanCopyUtils.copy(shopUser, TShopUserVo.class);
            }else{
                log.info("登录用户：{} 不存在.", phonenumber);
                throw new UserException("user.not.exists", phonenumber);
            }
        }
        return user;
    }
    /**
     * 退出登录
     */
    public void logout() {
        try {
            ShopLoginUser loginUser = ShopLoginHelper.getLoginUser();
            StpUtil.logout();
            recordLogininfor(String.valueOf(loginUser.getId()), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
        } catch (NotLoginException ignored) {
        }
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     * @return
     */
    private void recordLogininfor(String username, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
    }

    /**
     * 校验短信验证码
     */
    private boolean validateSmsCode(String phonenumber, String smsCode) {
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
        if (StringUtils.isBlank(code)) {
            recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        return code.equals(smsCode);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     */
    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
            throw new CaptchaException();
        }
    }

    /**
     * 构建登录用户
     */
    private ShopLoginUser buildLoginUser(TShopUserVo user) {
        ShopLoginUser loginUser = new ShopLoginUser();
        loginUser.setId(user.getId());
        loginUser.setNickName(user.getNickName());
        loginUser.setMobile(user.getMobile());
        loginUser.setAvatar(user.getAvatar());
        loginUser.setGender(user.getGender());
        loginUser.setWechatOpenId(user.getWechatOpenId());
        loginUser.setUserType(UserType.APP_USER.getUserType());
        return loginUser;
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId, String username) {
        TShopUser blogUser = new TShopUser();
        blogUser.setId(userId);
        blogUser.setLoginIp(ServletUtils.getClientIP());
        blogUser.setLastLoginTime(DateUtils.getNowDate());
        baseMapper.updateById(blogUser);
    }
    public void xcxRecordLoginInfo(Long userId, String openId) {
        TShopUser blogUser = new TShopUser();
        blogUser.setId(userId);
        blogUser.setLoginIp(ServletUtils.getClientIP());
        blogUser.setLastLoginTime(DateUtils.getNowDate());
        blogUser.setWechatOpenId(openId);
        baseMapper.updateById(blogUser);
    }
    /**
     * 登录校验
     */
    private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        String loginFail = Constants.LOGIN_FAIL;

        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
        // 锁定时间内登录 则踢出
        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
            recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
        }

        if (supplier.get()) {
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数 则递增
                RedisUtils.setCacheObject(errorKey, errorNumber);
                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
            }
        }

        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }
}
